﻿#define DEBUG
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.IO;
using System.Net;
using System.Text;
using System.Linq;
using System.Web;
using System.Text.RegularExpressions;
using System.Runtime.Serialization.Formatters.Binary;
using System.IO.Compression;
using System.Configuration;
using System.Collections.Specialized;
using System.Collections.Generic;

namespace ClientBackBone.EDDN
{
    public class CompaionConnector : IDisposable
    {
        private int loginCount = 0;
        private string lastProfile;
        private CookieContainer cookies; // (8D)
        public event EventHandler<EventArgs> ConfirmationRequired;
        public event EventHandler<EventArgs> LoginRequired;
        private string userAgent = @"Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257";
        public CompaionConnector()
        {
            Ship.lookUP = JObject.Parse(File.ReadAllText("./Component.json1"));
            if (File.Exists("./comp.auth"))
            {
                BinaryFormatter bin = new BinaryFormatter();
                using (Stream fileStream = File.OpenRead("./comp.auth"))
                {
                    this.cookies = (CookieContainer) bin.Deserialize(fileStream);
                }
            } 
            else 
            {
                this.cookies = new CookieContainer();
                this.cookies.Add(new Cookie()
                {
                    Name = "mid",
                    Value = "addcookiehereifyourhavingtrouble",
                    Path = "/",
                    Domain = "companion.orerve.net"
                });
                this.cookies.Add(new Cookie()
                {
                    Name = "mtk",
                    Value = "addcookiehereifyourhavingtrouble",
                    Path = "/",
                    Domain = "companion.orerve.net"
                });
                this.cookies.Add(new Cookie()
                {
                    Name = "CompanionApp",
                    Value = "addcookiehereifyourhavingtrouble",
                    Path = "/",
                    Domain = "companion.orerve.net"
                }); 
            }           
        }

        public bool HasCommodities()
        {
            bool has = false;
            JObject currentProfile = JObject.Parse(this.lastProfile);
            if((currentProfile["lastStarport"] != null) && (currentProfile["lastStarport"]["commodities"] != null))
            {
                var statname = currentProfile["lastStarport"]["id"].Value<string>();
                var shipId = currentProfile["commander"]["currentShipId"].Value<int>();                
                var ships = (currentProfile["ships"] as JObject);
                foreach (var ship in ships.Properties())
                {
                    if ((ship.Name == shipId.ToString() && string.CompareOrdinal(ship.Value["station"]["id"].Value<string>(),statname)==0))                        
                    {
                        has = true;
                    }
                }
            }
            
            return has;
        }

        public Ship GetShip()
        {
            return GetShip(DateTime.UtcNow, this.lastProfile);
        }

        private Ship GetShip(DateTime dateTime, string profile)
        {
            Ship ship = new Ship();
            JObject jObject = JObject.Parse(profile);
            JObject jShip = jObject["ship"] as JObject;
            ship.ShipType = jShip["name"].Value<string>();
            ship.ShipIndex = jShip["id"].Value<int>();
            ship.ShipName = ship.ShipType + "_" + ship.ShipIndex.ToString();
            ship.Hull = jShip["modules"]["Armour"]["module"]["name"].Value<string>().Substring(ship.ShipType.Length+1);
            ship.PowerGen = jShip["modules"]["PowerPlant"]["module"]["name"].Value<string>();
            ship.Thrusters = jShip["modules"]["MainEngines"]["module"]["name"].Value<string>();
            ship.FrameShift = jShip["modules"]["FrameShiftDrive"]["module"]["name"].Value<string>();
            ship.LifeSupport = jShip["modules"]["LifeSupport"]["module"]["name"].Value<string>();
            ship.PowerDistributer = jShip["modules"]["PowerDistributor"]["module"]["name"].Value<string>();
            ship.Sensors = jShip["modules"]["Radar"]["module"]["name"].Value<string>();
            ship.FuelTank = jShip["modules"]["FuelTank"]["module"]["name"].Value<string>();
            foreach (var module in ((JObject)jShip["modules"]).Properties())
            {
                var moduleObject = module.Value as JObject;

                if (module.Name.StartsWith("Slot"))
                {
                    if (moduleObject == null)
                    {
                        ship.Internals.Add("-");
                    }
                    else
                    {
                        ship.Internals.Add(moduleObject["module"]["name"].Value<string>());
                    }
                } else if (module.Name.StartsWith("Tiny"))
                {
                    if (moduleObject == null)
                    {
                        ship.Utilities.Add("-");
                    }
                    else
                    {
                        ship.Utilities.Add(moduleObject["module"]["name"].Value<string>());
                    }
                }
                else if (module.Name.Contains("Hardpoint"))
                {
                    if (moduleObject == null)
                    {
                        ship.HardPoints.Add("-");
                    }
                    else
                    {
                        ship.HardPoints.Add(moduleObject["module"]["name"].Value<string>());
                    }
                }
            }
           
            return ship;
        }

        public Commander GetCommander()
        {
            return GetCommander(DateTime.UtcNow,this.lastProfile);
        }

        public Commander GetCommander(DateTime time, string profile)
        {
            Commander commander = new Commander();
            JObject jObject = JObject.Parse(profile);
            var jCommander = jObject["commander"] as JObject;
            commander.alive = jCommander.Property("alive").Value.ToObject<bool>();
            commander.credits = jCommander.Property("credits").Value.ToObject<int>();
            commander.debt = 0-jCommander.Property("debt").Value.ToObject<int>();
            commander.docked = jCommander.Property("docked").Value.ToObject<bool>();
            commander.name = jCommander.Property("name").Value.ToString();
            commander.cargocapacity = jObject["ship"]["cargo"]["capacity"].Value<int>();
            commander.currentStation = jObject["lastStarport"]["name"].Value<string>();
            commander.currentsystem = jObject["lastSystem"]["name"].Value<string>();
            commander.rank = new Rank()
            {
                combat = jCommander["rank"]["combat"].Value<int>(),
                trade = jCommander["rank"]["trade"].Value<int>(),
                explore = jCommander["rank"]["explore"].Value<int>(),
                crime = jCommander["rank"]["crime"].Value<int>(),
                service = jCommander["rank"]["service"].Value<int>(),
                empire = jCommander["rank"]["empire"].Value<int>(),
                federation = jCommander["rank"]["federation"].Value<int>(),
                power = jCommander["rank"]["power"].Value<int>(),                
                cqc = jCommander["rank"]["cqc"] == null?0:jCommander["rank"]["cqc"].Value<int>()
            };
            commander.cargoValue = jObject["ship"]["value"]["cargo"].Value<int>();
            commander.shipValue = jObject["ship"]["value"]["total"].Value<int>() - commander.cargoValue;
            commander.date = time;
            Commander.BoundCommander(commander);
            return commander;
        }

        public Station GetStationComodities()
        {           
            JObject jObject = JObject.Parse(this.lastProfile);

            string system = jObject["lastSystem"]["name"].ToString();
            string station = jObject["lastStarport"]["name"].ToString();

            Station stat=new Station();
            stat.SystemName = system;
            stat.StationName = station;
            stat.updated = DateTime.UtcNow;
            stat.Comodities = 
                jObject["lastStarport"]["commodities"]
                    .Children()
                    .Select(s =>
                    {
                        var o = s as JObject;
                        return new Commodity
                        {  
                            BuyPrice= int.Parse(o.Property("buyPrice").Value.ToString()),
                            Demand = Convert.ToInt32(Double.Parse(o.Property("demand").Value.ToString())),
                            Name = o.Property("name").Value.ToString(),
                            SellPrice = int.Parse(o.Property("sellPrice").Value.ToString()),
                            Supply = Convert.ToInt32(Double.Parse(o.Property("stock").Value.ToString()))
                        };
                    }).ToDictionary(dic => dic.Name);
            return stat;
            
        }

        public bool GetProfile(bool forceUpdate)
        {
            HttpWebRequest req = HttpWebRequest.Create(@"https://companion.orerve.net/profile") as HttpWebRequest;
            req.Method = "GET";
            req.CookieContainer = this.cookies;
            req.UserAgent = userAgent;
            req.AllowAutoRedirect = false;
            req.Accept = @"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            req.Headers.Add(HttpRequestHeader.AcceptEncoding, @"gzip, deflate");
            req.Headers.Add(HttpRequestHeader.AcceptLanguage, @"en-US,en;q=0.5");
            req.UserAgent = this.userAgent;

            HttpWebResponse resp = req.GetResponse() as HttpWebResponse;

            string respString = string.Empty;
            string redirect = string.Empty;
            if (!string.IsNullOrEmpty(resp.Headers[HttpResponseHeader.Location]))
            {
                redirect = resp.Headers[HttpResponseHeader.Location];
            }

            
            if (redirect.EndsWith("login"))
            {
                if (this.loginCount++ > 1)
                {
                    this.RaiseEmailRequired();
                    this.loginCount--;
                }
                this.Login();
                bool return_val = this.GetProfile(forceUpdate);
                this.loginCount = 0;
                return return_val;
            }
            else
            {
                if (resp.ContentEncoding == "gzip")
                {
                    using(GZipStream zgstream = new GZipStream(resp.GetResponseStream(),CompressionMode.Decompress))
                    using (StreamReader reader2 = new StreamReader(zgstream))
                    {
                        respString = reader2.ReadToEnd();
                    }
                }
                else
                {
                    using (StreamReader reader2 = new StreamReader(resp.GetResponseStream()))
                    {
                        respString = reader2.ReadToEnd();
                    }
                }

                this.fixCookies(req, resp);
            }
            File.WriteAllText(DateTime.UtcNow.ToString("yyyyMMddHHmmssfff")+".json",respString);

            // abort if there are no changes.
            if (!forceUpdate && File.Exists("latest.response") && File.ReadAllText("latest.response").CompareTo(respString) == 0) return false;
            
            File.WriteAllText("latest.response", respString);

            this.lastProfile = respString;

            return true;

        }

        private void RaiseEmailRequired()
        {
            EventHandler<EventArgs> tmp = this.LoginRequired;
            if (tmp != null)
            {
                tmp(this, new EventArgs());
            }
        }

        public void LoadFromOld()
        {
            this.lastProfile = File.ReadAllText("latest.response");

        }

        private void Login()
        {
            ConfigurationManager.RefreshSection("appSettings");
            string email = ConfigurationManager.AppSettings["email"];
            string password = ConfigurationManager.AppSettings["password"];
            string code = ConfigurationManager.AppSettings["code"];

            if (string.IsNullOrEmpty(email) || string.IsNullOrEmpty(password) || email == "you@somesite.com")
            {
                RaiseEmailRequired();
                ConfigurationManager.RefreshSection("appSettings");
                email = ConfigurationManager.AppSettings["email"];
                password = ConfigurationManager.AppSettings["password"];
            }          

            HttpWebRequest req = HttpWebRequest.Create(@"https://companion.orerve.net/user/login") as HttpWebRequest;
            req.CookieContainer = this.cookies;
            req.AllowAutoRedirect = false;
            req.UserAgent = userAgent;
            req.Accept = @"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            req.Headers.Add(HttpRequestHeader.AcceptEncoding, @"gzip, deflate");
            req.Headers.Add(HttpRequestHeader.AcceptLanguage, @"en-US,en;q=0.5");
            req.UserAgent = @"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257";

            var resp = req.GetResponse() as HttpWebResponse;
            this.fixCookies(req, resp);            

            req = HttpWebRequest.Create(@"https://companion.orerve.net/user/login") as HttpWebRequest;
            req.Method = "POST";
            req.AllowAutoRedirect = false;
            req.CookieContainer = this.cookies;
            req.UserAgent = userAgent;
            req.Accept = @"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            req.Headers.Add(HttpRequestHeader.AcceptEncoding, @"gzip, deflate");
            req.Headers.Add(HttpRequestHeader.AcceptLanguage, @"en-US,en;q=0.5");
            req.UserAgent = @"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257";

            req.Referer = @"https://companion.orerve.net/user/login";
            req.ContentType = @"application/x-www-form-urlencoded";
            using (StreamWriter writer = new StreamWriter(req.GetRequestStream()))
            {
                string payload = "email=" + HttpUtility.UrlEncode(email) + "&password=" + HttpUtility.UrlEncode(password);
                writer.Write(payload);
                writer.Flush();
                resp = req.GetResponse() as HttpWebResponse;
            }

            this.fixCookies(req, resp);

            string redirect = string.Empty;
            if (!string.IsNullOrEmpty(resp.Headers[HttpResponseHeader.Location]))
            {
                redirect = resp.Headers[HttpResponseHeader.Location];
            }

            if (redirect.EndsWith("confirm"))
            {
                RaiseConfirm();                
                this.Confirm();
            }

            req = HttpWebRequest.Create(@"https://companion.orerve.net/") as HttpWebRequest;
            req.Method = "GET";
            req.AllowAutoRedirect = false;
            req.CookieContainer = this.cookies;
            req.UserAgent = userAgent;
            req.Referer = @"https://companion.orerve.net/user/login";
            req.Accept = @"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            req.Headers.Add(HttpRequestHeader.AcceptEncoding, @"gzip, deflate");
            req.Headers.Add(HttpRequestHeader.AcceptLanguage, @"en-US,en;q=0.5");
            req.UserAgent = @"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257";

            resp = req.GetResponse() as HttpWebResponse;
            string respString = string.Empty;
            using (StreamReader reader = new StreamReader(resp.GetResponseStream()))
            {
                respString = reader.ReadToEnd();
            }

            this.fixCookies(req, resp);
        }

        private void RaiseConfirm()
        {
            EventHandler<EventArgs> handler = this.ConfirmationRequired;
            if (handler != null)
            {
                handler(this, new EventArgs());
            }
        }

        private void Confirm()
        {
            ConfigurationManager.RefreshSection("appSettings");
            string email = ConfigurationManager.AppSettings["email"];
            string password = ConfigurationManager.AppSettings["password"];
            string code = ConfigurationManager.AppSettings["code"];

            if (string.IsNullOrEmpty(code) || (code == "CODER"))
            {
                this.RaiseConfirm();
                ConfigurationManager.RefreshSection("appSettings");
                code = ConfigurationManager.AppSettings["code"];
            }

            HttpWebResponse resp;
            HttpWebRequest req = HttpWebRequest.Create(@"https://companion.orerve.net/user/confirm") as HttpWebRequest;
            req.Method = "POST";
            req.CookieContainer = this.cookies;
            req.UserAgent = userAgent;
            req.AllowAutoRedirect = false;
            req.Accept = @"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8";
            req.Headers.Add(HttpRequestHeader.AcceptEncoding, @"gzip, deflate");
            req.Headers.Add(HttpRequestHeader.AcceptLanguage, @"en-US,en;q=0.5");
            req.UserAgent = @"User-Agent: Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Mobile/11D257";

            req.Referer = @"https://companion.orerve.net/user/confirm";
            req.ContentType = @"application/x-www-form-urlencoded";
            using (StreamWriter writer = new StreamWriter(req.GetRequestStream()))
            {
                string payload = "code=" + HttpUtility.UrlEncode(code);
                writer.Write(payload);
                writer.Flush();
                resp = req.GetResponse() as HttpWebResponse;
            }

            this.fixCookies(req, resp);
        }

        private void fixCookies(HttpWebRequest request, HttpWebResponse response)
        {
            for (int i = 0; i < response.Headers.Count; i++)
            {
                string name = response.Headers.GetKey(i);
                if (name != "Set-Cookie")
                    continue;
                string value = response.Headers.Get(i);
                Cookie cook = null;
                foreach (var part in value.Split(';'))
                {
                    var bits = part.Split('=');
                    bits[0] = bits[0].Trim();
                    if (bits.Length > 1)
                    {
                        bits[1] = bits[1].Trim();
                    }
                    bool secure = false;
                    if(bits[0].StartsWith("secure") && bits[0].Length > 6)
                    {
                        secure = true;
                        bits[0] = bits[0].Substring(7);
                    }

                    switch (bits[0])
                    {
                        case "domain": cook.Domain = bits[1]; break;
                        case "path": cook.Path = bits[1]; break;
                        case "expires": 
                            cook.Expires = DateTime.Parse(bits[1].Substring(0,25));
                            if (cook.Expires < DateTime.Now)
                            {
                                cook.Expires = cook.Expires.AddYears(1);
                            }
                            break;
                        case "secure": cook.Secure = true; break;
                        default:
                            if (cook != null)
                            {
                                if (string.IsNullOrEmpty(cook.Domain))
                                {
                                    cook.Domain = request.RequestUri.Host;
                                }

                                this.cookies.Add(cook);
                            }
                            cook = new Cookie(bits[0], bits[1]);
                            cook.Secure = secure;
                            break;
                    }
                }
                if (string.IsNullOrEmpty(cook.Domain))
                {
                    cook.Domain = request.RequestUri.Host;
                }

                this.cookies.Add(cook);
            }
        }        

        public void Dispose()
        {
            BinaryFormatter bin = new BinaryFormatter();
            using(Stream fileStream = File.OpenWrite("./comp.auth"))
            {
                bin.Serialize(fileStream, this.cookies);
                fileStream.Flush();
            }
        }
    }    
}